package com.example.user.controller.auth;

import com.example.framework.common.pojo.R;
import com.example.framework.security.config.SecurityProperties;
import com.example.framework.security.core.dto.JwtTokenRespDto;
import com.example.framework.security.core.util.SecurityFrameworkUtils;
import com.example.user.constants.LoginTypeEnum;
import com.example.user.controller.auth.vo.LoginReqVO;
import com.example.user.controller.auth.vo.PasswordUpdateVO;
import com.example.user.controller.auth.vo.RegisterReqVO;
import com.example.user.controller.basic.UserInfoController;
import com.example.user.controller.code.CodeController;
import com.example.user.service.auth.AuthService;
import com.example.user.service.login.LoginService;
import com.example.user.service.login.LoginServiceFactory;
import com.example.user.service.user.AccountService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;

@Validated
@Tag(name = "登录注册相关")
@RestController
public class AuthController {

    @Resource
    private AuthService authService;

    @Resource
    private SecurityProperties securityProperties;

    @Resource
    private AccountService accountService;

    @Resource
    private LoginServiceFactory loginServiceFactory;

    /**
     * 注册流程：
     * <ol>
     *     <li>输入邮箱校验用户是否已注册 {@link UserInfoController#checkUserExists}
     *     <li>未注册，发送验证码{@link CodeController#sendCode}；已注册，走下面两个接口登录
     *     <li>校验验证码{@link CodeController#verifyCode}
     *     <li>注册用户，此处携带要求携带验证码，防止突破前端直接到这一步
     * </ol>
     *
     * @param reqDto 注册信息
     * @return jwt token
     */
    @PermitAll
    @Operation(summary = "注册")
    @PostMapping("/register")
    public R<String> register(@Valid @RequestBody RegisterReqVO reqDto) {
        authService.register(reqDto);
        return R.success("注册成功");
    }

    /**
     * 登录返回双Token，accessToken有效期较短，refreshToken有效期较长
     * <p>
     * token泄露问题应对：
     * <ol>
     *     <li>accessToken过期而refreshToken没过期的话，可以通过refreshToken刷新获得新的accessToken，此时旧的accessToken就不可用了（可以防止盗用accessToken）</li>
     *     <li>如果refreshToken也被泄漏，被拿去更新accessToken，用户正常访问时就会出现登录问题，可以及时发现异常</li>
     * </ol>
     *
     * @param reqDto 用户名密码
     * @return jwt token
     */
    @PermitAll
    @Operation(summary = "登录")
    @PostMapping("/login")
    public R<JwtTokenRespDto> loginByPassword(@Valid @RequestBody LoginReqVO reqDto, HttpServletResponse response) {
        LoginService loginService = loginServiceFactory.get(LoginTypeEnum.get(reqDto.getLoginType()));
        JwtTokenRespDto jwtTokenRespDto = loginService.login(reqDto);
        response.addCookie(buildCookie(jwtTokenRespDto));
        return R.success(jwtTokenRespDto);
    }

    private Cookie buildCookie(JwtTokenRespDto jwtTokenRespDto) {
        // 将accessToken放在cookie中，后续浏览器访问接口时，自动带上cookie
        String jwtHeaderName = securityProperties.getHeaderName();
        Cookie cookie = new Cookie(jwtHeaderName, jwtTokenRespDto.getAccessToken());
        // 本地服务所有端口共享此cookie
        cookie.setDomain("localhost");
        // path 默认为当前 url 的一级路径，此处为 /login，之前接口是 /login，恰好导致 path 为 /
        cookie.setPath("/");
        // 这里简单起见不保存 refresh token 做刷新 token 的逻辑
        cookie.setMaxAge(Math.toIntExact(jwtTokenRespDto.getAccessTokenExpiresIn()));
        return cookie;
    }

    /**
     * 只要在refresh token有效期内访问过就能通过刷新token一直处于登录状态，如果超过refresh token有效期访问（长期不活跃）才需要重新登录
     *
     * @param refreshToken 刷新令牌
     * @return 新的 jwt token
     */
    @PermitAll
    @Operation(summary = "刷新Token")
    @GetMapping("/token/refresh")
    public R<JwtTokenRespDto> refreshToken(@NotBlank @RequestParam("refresh_token") String refreshToken) {
        JwtTokenRespDto jwtTokenRespDto = authService.refreshToken(refreshToken);
        return R.success(jwtTokenRespDto);
    }

    @PermitAll
    @Operation(summary = "重置密码——通过验证码")
    @PostMapping("/password/reset")
    public R<?> resetPassword(@Valid @RequestBody PasswordUpdateVO passwordUpdateVO) {
        authService.resetPassword(passwordUpdateVO);
        return R.success();
    }

    @Operation(summary = "删除账户")
    @PostMapping("/account/delete")
    public R<Boolean> deleteAccount() {
        String userId = SecurityFrameworkUtils.getLoginUserId();
        boolean deleted = accountService.deleteAccount(userId);
        return R.success(deleted);
    }

}
